package com.linln.core.utils;

import java.beans.Transient;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.asm.ClassReader;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;

/**
 * @author jiangmy
 * @date 2014年3月19日 下午5:51:22
 */
@SuppressWarnings("rawtypes")
public final class BeanUtils {

    private static final Logger logger = LoggerFactory.getLogger(BeanUtils.class);

    private static final Map<Class, Map<String, Field>> _fieldMap = new ConcurrentHashMap<>();
    private static final Map<Class, List<Field>> _fieldList = new ConcurrentHashMap<>();
    private static final Map<Class, List<Method>> _methodList = new ConcurrentHashMap<>();

    private BeanUtils() {
    }

    /**
     * @return 所有属性(包括父类, 去掉Transient的)
     */
    public static List<Field> getFields(Class<?> clz) {
        List<Field> list = _fieldList.get(clz);
        if (list == null) {
            list = new ArrayList<>();
            Map<String, Field> map = new HashMap<>();
            Field[] fields;
            while (!clz.equals(Object.class)) {
                fields = clz.getDeclaredFields();
                for (Field field : fields) {
                    if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
                        continue;
                    }
                    if (field.getAnnotation(Transient.class) != null) {
                        continue;
                    }
                    map.putIfAbsent(field.getName(), field);
                }
                clz = clz.getSuperclass();
            }
            list.addAll(map.values());
            _fieldList.put(clz, list);
        }
        return list;
    }

    /**
     * @return 字段名对应的Field映射Map
     */
    public static Map<String, Field> getFieldsMap(Class<?> clz) {
        Map<String, Field> map = _fieldMap.get(clz);
        if (map == null) {
            map = new ConcurrentHashMap<>();
            List<Field> list = getFields(clz);
            for (Field field : list) {
                map.put(field.getName(), field);
            }
            _fieldMap.put(clz, map);
        }
        return map;
    }

    /**
     * @return 获取clz类型中的property属性, 包含父类的属性, 不包含Transient和static属性
     */
    public static Field getField(Class clz, String property) {
        if (property == null) {
            return null;
        }
        if (property.contains(".")) {
            String p1 = property.split("\\.")[0];
            Field f = getField(clz, p1);
            return getField(f.getType(), property.substring(property.indexOf(".") + 1));
        }
        Map<String, Field> map = BeanUtils.getFieldsMap(clz);
        String key = camelName(underscoreName(property));
        Field field = map.get(key);
        return field;
    }

    /**
     * @return 获取一个类的所有方法
     */
    public static List<Method> getMethods(Class<?> clz) {
        List<Method> list = _methodList.get(clz);
        if (list == null) {
            list = new ArrayList<>();
            Method[] methods;
            while (!clz.equals(Object.class)) {
                methods = clz.getDeclaredMethods();
                for (Method method : methods) {
                    if (Modifier.isStatic(method.getModifiers()) || Modifier.isTransient(method.getModifiers())) {
                        continue;
                    }
                    if (method.getAnnotation(Transient.class) != null) {
                        continue;
                    }
                    list.add(method);
                }
                clz = clz.getSuperclass();
            }
            _methodList.put(clz, list);
        }
        return list;
    }

    /**
     * @return 获取clz类中对property的读方法
     */
    public static Method getReadMethod(Class clz, String property) {
        if (property == null) {
            return null;
        }
        List<Method> list = getMethods(clz);
        for (Method m : list) {
            if (m.getName().equalsIgnoreCase("get".concat(property)) || m.getName().equalsIgnoreCase("is".concat(property))) {
                return m;
            }
        }
        return null;
    }

    public static Field getField(Class clz, Method m) {
        Field f = null;
        if (m.getName().startsWith("get")) {
            f = BeanUtils.getField(clz, m.getName().substring(3));
        } else if (m.getName().startsWith("is")) {
            f = BeanUtils.getField(clz, m.getName().substring(2));
        } else if (m.getName().startsWith("set")) {
            f = BeanUtils.getField(clz, m.getName().substring(3));
        }
        return f;
    }

    /**
     * @return 获取clz类中对property的写方法
     */
    public static Method getWriteMethod(Class clz, String property) {
        if (property == null) {
            return null;
        }
        List<Method> list = getMethods(clz);
        for (Method m : list) {
            if (m.getName().equalsIgnoreCase("set".concat(property))) {
                return m;
            }
        }
        return null;
    }

    /**
     * @return 获取obj对象中property属性(含父类属性)的值
     */
    public static Object getPropertyValue(Object obj, String property) {
        Class clz = null;
        try {
            clz = obj.getClass();
            Field field = BeanUtils.getField(clz, property);
            if (field != null) {
                field.setAccessible(true);
                return field.get(obj);
            }
        } catch (Exception e) {
            BeanUtils.logger.error("getPropertyValue:" + property + " from " + clz, e);
        }
        return null;
    }

    public static <T extends Annotation> T getAnnotation(Class clz, String field, Method method, Class<T> annotation) {
        Field f = BeanUtils.getField(clz, field);
        if (field == null && method != null) {
            f = getField(clz, method);
            if (f != null) {
                field = f.getName();
            }
        }
        Method g = BeanUtils.getReadMethod(clz, field);
        T t = null;
        if (f != null) {
            t = f.getAnnotation(annotation);
        }
        if (t == null && g != null) {
            t = g.getAnnotation(annotation);
        }
        return t;
    }

    /**
     * 设置obj对象中property属性(含父类属性)的值为value
     */
    public static void setPropertyValue(Object obj, String property, Object value) {
        Class clz = null;
        try {
            clz = obj.getClass();
            if (value == null) {
                return;
            }
            Field field = getField(clz, property);
            if (field != null) {
                field.setAccessible(true);
                field.set(obj, cast(value, field.getType()));
            }
            if (logger.isTraceEnabled()) {
                logger.debug("setProperty:" + property + " value:" + value + " to " + clz);
            }
        } catch (Exception e) {
            BeanUtils.logger.info("Failed setProperty by Field:" + property + " value:" + value + " to " + clz, e);
            try {
                org.apache.commons.beanutils.BeanUtils.setProperty(obj, property, value);
            } catch (Exception e2) {
                BeanUtils.logger.warn("Error setProperty:" + property + " value:" + value + " to " + clz, e);
            }
        }
    }

    /**
     * @return bean实例转换为Map实例
     */
    public static Map<String, Object> toMap(Object bean) {
        Map<String, Object> map = new HashMap<>();
        if (bean == null) {
            return map;
        }
        Class<?> clz = bean.getClass();
        List<Field> fields = getFields(clz);
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                map.put(field.getName(), field.get(bean));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return map;
    }

    /**
     * @return Map实例转换为bean实例
     */
    public static <T> T toBean(Map<String, Object> map, Class<T> clz) {
        try {
            T bean = clz.newInstance();
            for (Entry<String, Object> entry : map.entrySet()) {
                BeanUtils.setPropertyValue(bean, entry.getKey(), entry.getValue());
            }
            return bean;
        } catch (Exception e) {
            BeanUtils.logger.error("copy values to bean:" + clz, e);
        }
        return null;
    }

    /**
     * @return 获取clz的所有属性名(包含super的属性)
     */
    public static List<String> getBeanFields(Class<?> clz) {
        List<String> list = new ArrayList<>();
        List<Field> fields = getFields(clz);
        for (Field field : fields) {
            list.add(field.getName());
        }
        return list;
    }

    /**
     * @return 获取bean对应的数据库字段名
     */
    public static List<String> getDbFields(Class<?> clz) {
        List<String> list = new ArrayList<>();
        List<Field> fields = getFields(clz);
        for (Field field : fields) {
            list.add(BeanUtils.underscoreName(field.getName()));
        }
        return list;
    }

    /**
     * @return 将数据库字段转化为Bean属性
     */
    public static List<String> getBeanFields(List<String> columns) {
        List<String> fields = new ArrayList<>();
        for (String column : columns) {
            fields.add(BeanUtils.camelName(column));
        }
        return fields;
    }

    /**
     * @return Bean属性转换成数据库字段
     */
    public static List<String> getDbFields(List<String> fields) {
        List<String> columns = new ArrayList<>();
        for (String field : fields) {
            columns.add(BeanUtils.underscoreName(field));
        }
        return fields;
    }

    /**
     * @return 复制src中的属性值到dst中
     */
    public static <S, D> D copyToBean(S src, D dst) {
        return copyToBean(src, dst, false, true);
    }

    public static <S, D> D copyToBean(S src, D dst, boolean copyAll, boolean overwrite) {
        Map<String, Object> map = toMap(src);
        try {
            for (Entry<String, Object> entry : map.entrySet()) {
                try {
                    if (ObjectUtils.isNotEmpty(entry.getValue()) || copyAll) {
                        Object dstValue = getPropertyValue(dst, entry.getKey());
                        if (ObjectUtils.isEmpty(dstValue) || overwrite) {
                            setPropertyValue(dst, entry.getKey(), entry.getValue());
                        }
                    }
                } catch (Exception e) {
                    logger.error(
                        "copy property:" + entry.getKey() + " value:" + entry.getValue() + " from " + src.getClass() + " to " + dst.getClass(), e);
                }
            }
        } catch (Exception e) {
            logger.error("copy property: from " + src.getClass() + " to " + dst.getClass(), e);
        }
        return dst;
    }

    /**
     * @return 调用本方法的类名
     */
    public static String getFileName() {
        return Thread.currentThread().getStackTrace()[2].getClassName();
    }

    /**
     * @return 调用本方法的方法名
     */
    public static String getMethodName() {
        return Thread.currentThread().getStackTrace()[2].getMethodName();
    }

    /**
     * @param name 转换前的驼峰式命名的字符串
     * @return 转换成下划线的命名
     */
    public static String underscoreName(String name) {
        StringBuilder result = new StringBuilder();
        if (name != null && name.length() > 0) {
            boolean isFirst = true;
            for (char s : name.toCharArray()) {
                if (isFirst) {
                    isFirst = false;
                    if (s >= 'A' && s <= 'Z') {
                        result.append((char) (s + 32));
                    } else {
                        result.append(s);
                    }
                    continue;
                }
                if (s >= 'A' && s <= 'Z') {
                    result.append("_");
                    result.append((char) (s + 32));
                } else {
                    result.append(s);
                }
            }
        }
        return result.toString();
    }

    /**
     * @param name 转换前的下划线大写方式命名的字符串
     * @return 转换成驼峰式命名的字符串
     */
    public static String camelName(String name) {
        return BeanUtils.camelName(name, false);
    }

    /**
     * @param upperFirst 是否默认转换第一个字符
     * @return 转换成驼峰式命名的字符串
     */
    public static String camelName(String name, boolean upperFirst) {
        StringBuilder result = new StringBuilder();
        if (name == null || name.isEmpty()) {
            return "";
        }
        name = name.toLowerCase();
        if (!name.contains("_")) {
            if (upperFirst) {
                return name.substring(0, 1).toUpperCase() + name.substring(1);
            } else {
                return name.substring(0, 1).toLowerCase() + name.substring(1);
            }
        }
        String camels[] = name.split("_");
        for (String camel : camels) {
            if (camel.isEmpty()) {
                continue;
            }
            if (result.length() == 0) {
                result.append(camel.toLowerCase());
            } else {
                result.append(camel.substring(0, 1).toUpperCase());
                result.append(camel.substring(1).toLowerCase());
            }
        }
        name = result.toString();
        if (upperFirst) {
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        } else {
            return name.substring(0, 1).toLowerCase() + name.substring(1);
        }
    }

    /**
     * @param types asm的类型({@link Type})
     * @param clazzes java 类型({@link Class})
     * @return 比较参数类型是否一致
     */
    public static boolean isSameType(Type[] types, Class<?>[] clazzes) {
        // 个数不同
        if (types.length != clazzes.length) {
            return false;
        }
        for (int i = 0; i < types.length; i++) {
            if (!Type.getType(clazzes[i]).equals(types[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * @return 获取方法的参数名
     */
    public static String[] getMethodParamNames(final Method m) {
        final String[] paramNames = new String[m.getParameterTypes().length];
        final String n = m.getDeclaringClass().getName();
        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassReader cr = null;
        try {
            cr = new ClassReader(n);
        } catch (IOException e) {
            BeanUtils.logger.error("method:" + m.getName(), e);
        }
        cr.accept(new ClassVisitor(Opcodes.ASM4, cw) {

            @Override
            public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature,
                    final String[] exceptions) {
                final Type[] args = Type.getArgumentTypes(desc);
                if (!name.equals(m.getName()) || !BeanUtils.isSameType(args, m.getParameterTypes())) {
                    return super.visitMethod(access, name, desc, signature, exceptions);
                }
                MethodVisitor v = this.cv.visitMethod(access, name, desc, signature, exceptions);
                return new MethodVisitor(Opcodes.ASM4, v) {

                    @Override
                    public void visitLocalVariable(String name, String desc, String signature, org.springframework.asm.Label start,
                            org.springframework.asm.Label end, int index) {
                        int i = index - 1;
                        if (Modifier.isStatic(m.getModifiers()) || Modifier.isTransient(m.getModifiers())) {
                            i = index;
                        }
                        if (i >= 0 && i < paramNames.length) {
                            paramNames[i] = name;
                        }
                        super.visitLocalVariable(name, desc, signature, start, end, index);
                    }
                };
            }
        }, 0);
        return paramNames;
    }

    public static void testGetClassName() {
        // 方法1：通过SecurityManager的保护方法getClassContext()
        String clazzName = new SecurityManager() {

            public String getClassName() {
                return getClassContext()[1].getName();
            }
        }.getClassName();
        System.out.println(clazzName);
        // 方法2：通过Throwable的方法getStackTrace()
        String clazzName2 = new Throwable().getStackTrace()[1].getClassName();
        System.out.println(clazzName2);
        // 方法3：通过分析匿名类名称()
        String clazzName3 = new Object() {

            public String getClassName() {
                String clazzName = this.getClass().getName();
                return clazzName.substring(0, clazzName.lastIndexOf('$'));
            }
        }.getClassName();
        System.out.println(clazzName3);
        // 方法4：通过Thread的方法getStackTrace()
        String clazzName4 = Thread.currentThread().getStackTrace()[2].getClassName();
        System.out.println(clazzName4);
    }

    public static void testGetFunctionName() {
        // 方法1：通过Throwable的方法getStackTrace()
        String funcName2 = new Throwable().getStackTrace()[1].getMethodName();
        System.out.println(funcName2);
        // 方法2：通过Thread的方法getStackTrace()
        String clazzName4 = Thread.currentThread().getStackTrace()[2].getMethodName();
        System.out.println(clazzName4);
    }

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object o, Class<T> type) {
        if (o == null) {
            return null;
        }
        if (o.getClass() == type) {
            return (T) o;
        }
        if (type.isInstance(o)) {
            return (T) o;
        }
        if (type == char.class || type == Character.class) {
            return (T) Character.valueOf(o.toString().charAt(0));
        }
        if (type == Timestamp.class) {
            return (T) new Timestamp(DateUtils.str2Date(o.toString(), "yyyy-MM-dd HH:mm:ss").getTime());
        }
        if (type == Date.class) {
            return (T) DateUtils.str2Date(o.toString(), "yyyy-MM-dd HH:mm:ss");
        }
        return JacksonUtils.fromJson(o.toString(), type);
    }

    public static Object invoke(Object obj, String method, Object... params) {
        Class[] paramTypes = null;
        if (params != null) {
            paramTypes = new Class[params.length];
            for (int i = 0; i < params.length; i++) {
                paramTypes[i] = params[i].getClass();
            }
        }
        try {
            Method m = obj.getClass().getDeclaredMethod(method, paramTypes);
            Object object = m.invoke(obj, params);
            return object;
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        System.out.println(BeanUtils.camelName("ticket_name", false));
        System.out.println(BeanUtils.camelName("ticket_name", true));
        System.out.println(BeanUtils.camelName("TABLES", true));
    }
}
